05 - Wykrywanie krawędzi i konturów, dopasowywanie cech
Przetwarzanie i Analiza Obrazów
Politechnika Poznańska, Instytut Robotyki i Inteligencji Maszynowej
Ćwiczenie laboratoryjne 5: Wykrywanie krawędzi i konturów, dopasowywanie cech.
Powrót do spisu treści ćwiczeń laboratoryjnych
W tym ćwiczeniu:
Jak wykrywać krawędzie w obrazach i jakie są popularne algorytmy do tego celu?
Jak znaleźć i analizować kontury w obrazach? Cechy konturów i ich zastosowania.
Jak dopasowywać cechy między obrazami i jakie są popularne metody dopasowywania?
1. Cel ćwiczenia
Celem ćwiczenia jest nauczenie się wykrywania i analizy krawędzi oraz konturów w obrazach oraz prostych metod dopasowywania cech między obrazami.
2. Wykrywanie krawędzi
Wykrywanie krawędzi to podstawowy krok w wielu algorytmach analizy obrazu. Popularne metody obejmują operatory Sobela, Prewitta, Laplace, a w praktycznych zastosowaniach najczęściej używa się algorytmu Canny.
Operatory Sobela i Prewitta
Operatory Sobela i Prewitta to klasyczne filtry gradientowe pokazujące kierunki i siłę krawędzi. Sobel korzysta z wag, które dają większe znaczenie centralnym pikselom; Prewitt można zaimplementować jako konwolucję z odpowiednimi kernelami.
Filtr Prewitta: Prosty filtr gradientowy, który można zaimplementować jako konwolucję z dwoma kernelami (dla kierunku x i y) - służy do wykrywania krawędzi w poziomie i pionie.
Filtr Sobela: Podobny do Prewitta, ale z wagami, które dają większe znaczenie centralnym pikselom, co pozwala na lepsze wykrywanie krawędzi przy obecności szumu.
Przykłady:
import cv2
import numpy as np
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
img_blur = cv2.GaussianBlur(img, (3,3), 0)
# Filtr Prewitta
kernelx = np.array([
[-1,0,1],
[-1,0,1],
[-1,0,1]], dtype=np.float32)
kernely = np.array([
[1,1,1],
[0,0,0],
[-1,-1,-1]], dtype=np.float32)
prewittx = cv2.filter2D(img_blur, -1, kernelx)
prewitty = cv2.filter2D(img_blur, -1, kernely)
prewitt_mag = cv2.convertScaleAbs(np.sqrt(np.float32(prewittx)**2 + np.float32(prewitty)**2))
cv2.imshow('Prewitt X', prewittx)
cv2.imshow('Prewitt Y', prewitty)
cv2.imshow('Prewitt Magnitude', prewitt_mag)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Filtr Sobela
sobelx = np.array([
[-1,0,1],
[-2,0,2],
[-1,0,1]], dtype=np.float32)
sobely = np.array([
[1,2,1],
[0,0,0],
[-1,-2,-1]], dtype=np.float32)
sobelx = cv2.filter2D(img_blur, -1, sobelx)
sobely = cv2.filter2D(img_blur, -1, sobely)
sobel_mag = np.sqrt(sobelx**2 + sobely**2)
sobel_mag = np.uint8(np.clip(sobel_mag / np.max(sobel_mag) * 255, 0, 255))
cv2.imshow('Sobel X', sobelx)
cv2.imshow('Sobel Y', sobely)
cv2.imshow('Sobel Magnitude', sobel_mag)
cv2.waitKey(0)
cv2.destroyAllWindows()Wskazówki:
Prewitt pokazuje podobne informacje co Sobel, ale z prostszymi wagami; warto użyć ich do demonstracji kierunków krawędzi.
Dla dalszego przetwarzania często progować się na magnitudzie gradientu aby uzyskać binarną mapę krawędzi.
Algorytm Canny
Algorytm Canny (dwustopniowe progowanie) składa się z: wygładzenia (zwykle filtr Gaussa), obliczenia gradientów, nie-maksimum suppression oraz hysteresis thresholding (dwa progi: niski i wysoki).
Wygładzanie: Usuwa szum, który może powodować fałszywe krawędzie.
Obliczanie gradientów: Używa operatorów Sobela do obliczenia kierunku i siły krawędzi.
Nie-maksimum suppression: Usuwa piksele, które nie są lokalnymi maksimami w kierunku gradientu.
Pętla histerezy: Łączy krawędzie, które są powyżej progu wysokiego, oraz te, które są powyżej progu niskiego i są połączone z krawędziami powyżej progu wysokiego.
Przykład użycia:
import cv2
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
blur = cv2.GaussianBlur(img, (5,5), 1.4)
edges = cv2.Canny(blur, threshold1=50, threshold2=150)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()Wskazówki: - Dostosuj progi threshold1 i
threshold2 do kontrastu obrazu (zwykle
threshold2 ≈ 2-3 × threshold1).
- Przed Canny warto wygładzić obraz (np.
GaussianBlur) aby usunąć drobny szum.
3. Znajdowanie i analiza konturów
Kontury to krzywe ograniczające obszary o tej samej intensywności
(zwykle po binarizacji). W OpenCV używa się
cv2.findContours. Fantastyczne narzędzie do analizy
kształtów, rozmiarów i relacji przestrzennych obiektów.
Podstawowe kroki:
Przygotuj obraz binarny (np. progowanie, Canny).
Wywołaj
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE).Analizuj kontury:
cv2.contourArea,cv2.arcLength,cv2.boundingRect,cv2.minEnclosingCircle,cv2.approxPolyDP,cv2.moments.
Przykład:
import cv2
import numpy as np
img = cv2.imread('image.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Sortowanie konturów po polu (malejąco)
contours_sorted = sorted(contours, key=cv2.contourArea, reverse=True)
for i, cnt in enumerate(contours_sorted[:5]):
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
approx = cv2.approxPolyDP(cnt, 0.02*cv2.arcLength(cnt, True), True)
M = cv2.moments(cnt)
if M['m00'] != 0:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
else:
cx, cy = 0, 0
cv2.drawContours(img, [cnt], -1, (0,255,0), 2)
cv2.circle(img, (cx,cy), 3, (0,0,255), -1)
cv2.imshow('Contours', img)
cv2.waitKey(0)
cv2.destroyAllWindows()Hierarchia konturów (argument hierarchy) pozwala
rozróżnić kontury zagnieżdżone (np. obiekty i otwory).
hierarchy to macierz, gdzie dla każdego konturu mamy indeks
następnego, poprzedniego, pierwszego potomka i rodzica.
Wskazówki i zastosowania:
Użyj sortowania po polu (
cv2.contourArea) aby wybrać największe obiekty.approxPolyDPpozwala uprościć kontur i wykrywać wielokąty (np. prostokąty, trójkąty).Momentów (
cv2.moments) używamy do obliczenia środka ciężkości i cech takich jak invariants Hu.Hierarchia (
cv2.RETR_TREE,cv2.RETR_CCOMP) jest przydatna do rozróżniania konturów zewnętrznych i dziur.
4. Dopasowywanie cech — Template Matching (dopasowywanie szablonu)
Template matching to prosty sposób na znalezienie małego wzorca w
większym obrazie. OpenCV oferuje cv2.matchTemplate, która
zwraca mapę podobieństwa; następnie używamy cv2.minMaxLoc
aby znaleźć najlepsze dopasowania.
Przykład prosty:
import cv2
import numpy as np
img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
template = img[500:600, 500:600] # Przykładowy fragment obrazu jako szablon
th, tw = template.shape[:2]
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# Dla metody TM_SQDIFF najlepszy wynik to min_loc
top_left = max_loc
bottom_right = (top_left[0] + tw, top_left[1] + th)
cv2.rectangle(img, top_left, bottom_right, (0,255,0), 2)
cv2.imshow('Selected Template', template)
cv2.imshow('Template Matching', img)
cv2.waitKey(0)
cv2.destroyAllWindows()Wskazówki:
Wybierz odpowiednią metodę:
TM_CCOEFF_NORMEDjest często dobrym wyborem;TM_SQDIFFiTM_SQDIFF_NORMEDszukają minimum.Skalowanie i rotacja ograniczają skuteczność prostego dopasowywania szablonów — w takich przypadkach rozważ dopasowywanie cech (SIFT/ORB) lub dopasowanie wielkoskalowe.
Można progować mapę wyników
resaby znaleźć wszystkie lokalizacje powyżej zadanej wartości progowej.
5. Zadania do samodzielnego wykonania
Każde zadanie wykonaj w osobnym pliku Python (zad1.py,
zad2.py, …).
💥 Zadanie 1 - Wykrywanie krawędzi w HSV 💥
Wczytaj dowolny obraz kolorowy na którym widoczne są znaki drogowe.
Przekonwertuj go do przestrzeni HSV.
Utwórz dwie maski dla kolorów odpowiadających widocznym znakom drogowym (np. czerwony i niebieski, w zależności od obrazu). Wykorzystaj kolorowe progowanie (np.
cv2.inRange) aby uzyskać binarne maski dla tych kolorów.Na każdej masce wykonaj operacje morfologiczne by usunąć szum i poprawić kształt obiektów.
Wykonaj wykrywanie konturów na każdej masce i narysuj je na oryginalnym obrazie, oznaczając odpowiednio kolorem (np. czerwonym dla czerwonych znaków, niebieskim dla niebieskich).
Wyświetl wynik.
💥 Zadanie 2 - Dopasowywanie szablonu 💥
Napisz program oznaczający automatycznie pomarańcze i jabłka dla sortowni owoców. Wykorzystaj prosty template matching, gdzie szablonami będą fragmenty zdjęć przedstawiające pojedyncze owoce.
Wykorzystaj zdjęcie.
Dla robota zbierającego wystarczy, że owoce dwóch rodzajów będą otoczone obwódkami lub prostokątami w różnych kolorach.
💥 Zadanie 3 - Zadanie otwarte 💥
Policz automatycznie jaka kwota znajduje się na obrazie coins.jpg. Sumę monet wyświetl w terminalu z dokładnością do 2 miejsc po przecinku, wykorzystując f-string. Możesz wykorzystać dowolne metody (np. wykrywanie krawędzi, konturów, dopasowywanie szablonu) — ważne jest, aby program był w stanie rozróżnić monety o różnych nominałach i zsumować ich wartość.